/*
 * Copyright 2006-2007 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package sant

import com.google.code.sant.{SantProject, Env, Ant}
import com.google.code.sant.util.ClasspathUtils._

import scala.io.Source
import java.io.File
import java.net.{URL,URLClassLoader}
import java.lang.reflect.{InvocationTargetException,Modifier}

import scala.collection.mutable.Queue

import scala.tools.nsc._
import scala.tools.nsc.io.PlainFile
import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.nsc.util.{CompoundSourceFile, SourceFile, SourceFileFragment} 

/**
 * This class responsible for reading build scripts from file and parsing it.
 *
 * This is a simple "dirty" implementation which "just work" at the moment. 
 * Heavy refactoring needed.
 *
 * @author Sergey Nebolsin
 */
object SantScriptLoader {
  def santHome = Env.santHome
  def santLibDir = Env.santLibDir
  def santJar = Env.santJar
  def santClasspath = santJar.getPath :: jarsFromDir(santLibDir.getPath).filter( jar => !jar.endsWith("sant.jar") )
  
  // XXX: should not be reused
  val settings = {
    val s = new GenericRunnerSettings(Console.println _)
    s.classpath.tryToSet(List("-cp", santClasspath.mkString(File.pathSeparator)))
    s
  }
  
  var queueDirsForDeletion = new Queue[File]()
  
  def checkConfigured() = {
    val cl = new URLClassLoader( List( fileToURL(santJar).get ).toArray )
    try {
      Class.forName("sant.Predef", true, cl)
      true
    } catch {
      case e => throw new SantScriptLoadingException("sant.jar not found")
    }
  }
  
  def cleanup {
    Ant.project.verbose("cleaning up")
    
    import Interpreter.deleteRecursively
    queueDirsForDeletion.foreach(deleteRecursively _)
  }
   
  def loadFromFile(scriptFile: File) {
    Ant.project.verbose("loading script from file " + scriptFile)
    
    checkConfigured()
    val (main, compiledPath) = compileScript( scriptFile )
      
    val classpath = 
      paths(settings.bootclasspath.value) ::: 
      paths(compiledPath) ::: 
      paths(settings.classpath.value)

    try {
      val currentLoader = getClass().getClassLoader()
      Ant.project.verbose("classpath: " + classpath.mkString(":"))
      val parentClassLoaderClasspath = currentLoader match {
        case u: URLClassLoader => u.getURLs.mkString(":")
        case x => x.toString
      }
      Ant.project.verbose("parent classloader's classpath: " + parentClassLoaderClasspath)
      val extendedLoader = new URLClassLoader(classpath.toArray, currentLoader )
      val classToRun = Class.forName(main, true, extendedLoader)
      val method = classToRun.getMethod("main", List(classOf[Array[String]]).toArray)
      Ant.project.verbose("invoking main")
      method.invoke(null, Array(null))
    } catch {
      case e =>
        e.printStackTrace
        throw new SantScriptLoadingException("cannot load compiled build script '" + scriptFile + "': " + e, e)
    } 
  }
   
  def compileScript(file: File): (String, String) = {
    val compiledPath = File.createTempFile("santscript", "")
    compiledPath.delete
    compiledPath.mkdirs
    // schedule created temp dir for deletion
    queueDirsForDeletion += compiledPath
    
    settings.outdir.value = compiledPath.getPath
    val reporter = new ConsoleReporter(settings)
    val compiler = new Global(settings, reporter)
    val cr = new compiler.Run
    val (main, script) = wrappedScript(file, compiler.getSourceFile _)
    cr.compileSources(List(script))
    if( !reporter.hasErrors ) {
      (main, compiledPath.getPath)
    } else {
      throw new SantScriptLoadingException("cannot compile build script '" + file + "'")
    }
  }
  
  def wrappedScript(file: File, getSourceFile: PlainFile => SourceFile): (String, SourceFile) = {
    // XXX: this is dirty hack
    val n = System.currentTimeMillis() + "x" + new java.util.Random().nextInt(100000)
    val packageName = "$santscript"
    val mainObjectName = "Main" + n
    
    val preamble =
      new SourceFile("<script preamble>",
          ("package " + packageName + "\n" +
          "object " + mainObjectName + " {\n" +
          "  def main(argv: Array[String]): Unit = {\n" +
          "  val args = argv;\n").toCharArray)
    val middle = getSourceFile(new PlainFile(file))
    val end = new SourceFile("<script trailer>", "\n} }\n".toCharArray)
    (packageName + "." + mainObjectName, new CompoundSourceFile(preamble, middle, end))
  }

}

/**
 * @author Sergey Nebolsin
 */
class SantScriptLoadingException(message: String, cause: Throwable) extends Exception(message, cause) {
  def this(message:String) = this(message, null)
}
